﻿using System;
using System.Diagnostics.Contracts;

namespace Leek.Persistence
{
    public abstract class DomainObject<T, TId> : IDomainObject
        where T : DomainObject<T, TId>
    {
        private readonly DomainContext _context;
        private readonly DomainObjectId<T, TId> _id;
        private DomainObjectState _state;


        protected internal DomainContext Context
        {
            [Pure]
            get { return _context; }
        }

        public DomainObjectId<T, TId> Id
        {
            [Pure]
            get { return _id; }
        }

        public DomainObjectState State
        {
            [Pure]
            get { return _state; }
            protected internal set
            {
                _state = value; //TODO: Validate the graph of valid state changes!
            }
        }

        DomainObjectState IDomainObject.State
        {
            [Pure]
            get { return _state; }
            set { State = value; }
        }


        protected internal DomainObject(DomainContext context, TId id, DomainObjectState initialState)
        {
            _context = context;
            _id = new DomainObjectId<T, TId>(context, id);
            var objectWithIdAlreadyExists = _context.ContainsId<T, TId>(_id);

            switch (initialState)
            {
                case DomainObjectState.New:
                    if (objectWithIdAlreadyExists)
                        throw new DuplicateDomainObjectException();
                    else
                        _state = DomainObjectState.New;
                    break;
                case DomainObjectState.Persisted:
                    if (objectWithIdAlreadyExists)
                        _state = DomainObjectState.UntrackedDuplicate;
                    else
                        _state = DomainObjectState.Persisted;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("initialState",
                        "A DomainObject may only be created with an initial state of New or Persisted.");
            }

            if (_state != DomainObjectState.UntrackedDuplicate)
                _context.Register(this);
        }


        void IDomainObject.Persist()
        {
            var repository = CompositionContainer.GetInstance<IDomainObjectRepository<T, TId>>();

            switch (_state)
            {
                case DomainObjectState.New:
                    repository.Add((T)this);
                    _state = DomainObjectState.Persisted;
                    break;
                case DomainObjectState.Modified:
                    repository.Update((T)this);
                    _state = DomainObjectState.Persisted;
                    break;
                case DomainObjectState.PendingDeletion:
                    repository.Delete((T)this);
                    _state = DomainObjectState.Deleted;
                    break;
                case DomainObjectState.Deleted:
                    throw new DomainObjectDeletedException();
                case DomainObjectState.Persisted:
                    break;
                case DomainObjectState.UntrackedDuplicate:
                    throw new UntrackedDomainObjectException();
            }
        }


        public override bool Equals(object obj)
        {
            var other = obj as DomainObject<T, TId>;
            if (other == null)
                return false;
            else
                return Id.Equals(other.Id);
        }

        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }

        public override string ToString()
        {
            return Id.ToString();
        }
    }
}
